package edu.ucla.wanda.social;

import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.data.time.Millisecond;
import org.jfree.data.time.RegularTimePeriod;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.primefaces.event.ItemSelectEvent;
import org.primefaces.model.DefaultStreamedContent;
import org.primefaces.model.StreamedContent;
import org.primefaces.model.chart.CartesianChartModel;
import org.primefaces.model.chart.ChartSeries;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import edu.ucla.wanda.db.*;

@ManagedBean
@SessionScoped
public class UserSession {
	private String studyUserID, password;
	private WandaSubjectDbAdapter subjectDbAdapter;
	private WandaSocialDbAdapter socialDbAdapter;
	private FacebookUserAdapter facebookUserAdapter;
	private FacebookAppAdapter facebookAppAdapter;
	private ArrayList<Map.Entry<String, String>> messages;
	private DateTimeZone userTimezone;
	private WandaVisualization wandaVisualization;

	private static final String APPLICATION_ID = "321155711249351";
	private static final int BUF_SIZ = 64 * 1024;

	public UserSession() throws IOException {
		setFacebookAppAdapter(new FacebookAppAdapter(APPLICATION_ID));
		messages = new ArrayList<Map.Entry<String, String>>();
		wandaVisualization = new WandaVisualization();

		// TODO: delete
		appendMessage("message 1");
		appendMessage("message 2");
		appendMessage("fdsgfsfdgfdgdfdfgdfsg dfgf dg dfg dsfg fdsgdf");
	}

	public String getStudyUserID() {
		return studyUserID;
	}

	public void setStudyUserID(String user) {
		this.studyUserID = user;
	}

	public String getName() throws IOException {
		return facebookUserAdapter.getMeField("name");
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String pass) {
		this.password = pass;
	}

	public WandaSubjectDbAdapter getSubjectDbAdapter() {
		return subjectDbAdapter;
	}

	public void setSubjectDbAdapter(WandaSubjectDbAdapter subjectDbAdapter) {
		this.subjectDbAdapter = subjectDbAdapter;
	}

	public WandaSocialDbAdapter getSocialDbAdapter() {
		return socialDbAdapter;
	}

	public void setSocialDbAdapter(WandaSocialDbAdapter socialDbAdapter) {
		this.socialDbAdapter = socialDbAdapter;
	}

	/**
	 * @return the facebookAdapter
	 */
	public FacebookUserAdapter getFacebookUserAdapter() {
		return facebookUserAdapter;
	}

	/**
	 * @param facebookAdapter
	 *            the facebookAdapter to set
	 */
	public void setFacebookAdapter(FacebookUserAdapter facebookUserAdapter) {
		this.facebookUserAdapter = facebookUserAdapter;
		try {
			int i = Math.round(2 * Float.parseFloat(facebookUserAdapter
					.getMeField("timezone")));

			if ((i % 2) != 0) {
				userTimezone = DateTimeZone.forOffsetHoursMinutes(i / 2, 30);
			}

			else {
				userTimezone = DateTimeZone.forOffsetHours(i / 2);
			}
		}

		catch (Exception e) {
			appendMessage("Warning: Facebook timezone not properly set.  Times will be displayed using UTC.");
			userTimezone = DateTimeZone.forOffsetHours(0);
		}
	}

	/**
	 * @return the appAdapter
	 */
	public FacebookAppAdapter getFacebookAppAdapter() {
		return facebookAppAdapter;
	}

	/**
	 * @param appAdapter
	 *            the appAdapter to set
	 */
	public void setFacebookAppAdapter(FacebookAppAdapter facebookAppAdapter) {
		this.facebookAppAdapter = facebookAppAdapter;
	}

	/**
	 * @return the messages
	 */
	public ArrayList<Map.Entry<String, String>> getMessages() {
		return messages;
	}

	/**
	 * @param messages
	 *            the messages to set
	 */
	public void setMessages(ArrayList<Map.Entry<String, String>> messages) {
		this.messages = messages;
	}

	public void clearMessages() {
		messages.clear();
	}

	public void appendMessage(String message) {
		String time = DateTime.now().withZone(userTimezone)
				.toString("MMM dd: h:mm:ss a");
		messages.add(new AbstractMap.SimpleEntry<String, String>(time, message));
	}

	public boolean refreshToken() throws IOException {
		URL url = new URL(
				"https://graph.facebook.com/oauth/access_token?client_id=321155711249351&redirect_uri="
						+ "&client_secret=9b23e065d8a01768276c21446fc2f67f&code="
						+ socialDbAdapter.getUser_id());

		HttpURLConnection cxn = (HttpURLConnection) url.openConnection();

		cxn.setRequestMethod("GET");
		cxn.setRequestProperty("Accept", "application/json");

		return true;

	}

	public String DoStudyAuthentication() throws IOException {
		if (null == studyUserID || null == password) {
			return "failure";
		}

		subjectDbAdapter = new WandaSubjectDbAdapter(studyUserID);

		// correct password - store the user id in the social database to bind
		// the two accounts
		if (subjectDbAdapter.Authenticate(password)) {
			socialDbAdapter.setWanda_subject_id(studyUserID);
			return "success";
		}

		else {
			return "failure";
		}
	}

	/**
	 * @return the wandaVisualization
	 */
	public WandaVisualization getWandaVisualization() {
		return wandaVisualization;
	}

	public class WandaVisualization {
		// chart objects
		private HashMap<String, String> activeChartSeries;

		// for primefaces live charts
		private CartesianChartModel categoryChartModel;

		// for streamed image charts
		private StreamedContent streamedChartObject; // for streamed image
														// charts
		private boolean streamedChartStale;
		private ByteArrayInputStream chartStream;

		public WandaVisualization() {
			this.activeChartSeries = new HashMap<String, String>();
			this.categoryChartModel = new CartesianChartModel();
			this.streamedChartStale = false;
		}

		public void updateCategoryChartModel() {
			HashMap<String, ChartSeries> allSeries = new HashMap<String, ChartSeries>();

			for (WandaSensorMeasurement m : subjectDbAdapter.getMeasurements()) {
				// skip measurements we don't care about
				if (!activeChartSeries.containsKey(m.getSensorId())) {
					continue;
				}

				// create XYDataList if it doesn't exist and add to hash
				if (!allSeries.containsKey(m.getSensorId())) {
					ChartSeries series = new ChartSeries();

					series.setLabel(m.getSensorSpec().getType() + " ("
							+ m.getSensorId() + ")");
					allSeries.put(m.getSensorId(), series);
				}

				ChartSeries series = allSeries.get(m.getSensorId());

				series.set(m.getStartTimeLong(), m.getValueDouble());
			}

			// add all chart series corresponding to the active set to the chart
			// model
			for (Map.Entry<String, ChartSeries> seriesMap : allSeries
					.entrySet()) {
				categoryChartModel.addSeries(seriesMap.getValue());
			}
		}

		public void itemSelect(ItemSelectEvent event) {
			FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_INFO,
					"Item selected", "Item Index: " + event.getItemIndex()
							+ ", Series Index:" + event.getSeriesIndex());

			FacesContext.getCurrentInstance().addMessage(null, msg);
		}

		public void setSeriesToChart(String series) {
			activeChartSeries.clear();
			activeChartSeries.put(series, series);
			updateCategoryChartModel();
			streamedChartStale = true;
		}

		public void addSeriesToChart(String series) {
			if (activeChartSeries.containsKey(series)) {
				return;
			}

			activeChartSeries.put(series, series);
			updateCategoryChartModel();
			streamedChartStale = true;
		}

		/**
		 * @return the chartObject
		 */
		public StreamedContent getStreamedChartObject() {
			if (streamedChartStale) {
				updateStreamedChart();
			}

			return streamedChartObject;
		}

		/**
		 * @return the categoryChartModel
		 */
		public CartesianChartModel getCategoryChartModel() {
			return categoryChartModel;
		}

		public void updateStreamedChart() {
			HashMap<String, TimeSeries> allSeries = new HashMap<String, TimeSeries>();

			for (WandaSensorMeasurement m : subjectDbAdapter.getMeasurements()) {
				// skip measurements we don't care about
				if (!activeChartSeries.containsKey(m.getSensorId())) {
					continue;
				}

				// create TimeSeries if it doesn't exist and add to hashmap
				if (!allSeries.containsKey(m.getSensorId())) {
					TimeSeries series = new TimeSeries(m.getSensorSpec()
							.getType() + "(" + m.getSensorId() + ")");
					allSeries.put(m.getSensorId(), series);
				}

				TimeSeries series = allSeries.get(m.getSensorId());
				// TODO: delete?
				// Millisecond ms = new Millisecond(new
				// Date(m.getStartTimeLong()), userTimezone.toTimeZone(),
				// Locale.US);
				RegularTimePeriod r = RegularTimePeriod.createInstance(
						Millisecond.class, new Date(m.getStartTimeLong()),
						userTimezone.toTimeZone());

				try {
					series.add(r, m.getValueDouble());
				} catch (Exception e) {
					e.printStackTrace();
					continue;
				}
			}

			TimeSeriesCollection chartSeriesCollection = new TimeSeriesCollection(
					userTimezone.toTimeZone());

			// add all chart series corresponding to the active set to the chart
			// model
			for (Map.Entry<String, TimeSeries> seriesMap : allSeries.entrySet()) {
				chartSeriesCollection.addSeries(seriesMap.getValue());
			}

			String name;
			try {
				name = facebookUserAdapter.getMeField("first_name") + "'s";
			} catch (IOException e) {
				name = "My";
			}

			JFreeChart jFreeChart = ChartFactory.createTimeSeriesChart(name
					+ " sensor readings", "Time of Sensor Reading",
					"Value of Sensor Reading", chartSeriesCollection, true,
					true, false);

			ByteArrayOutputStream out = new ByteArrayOutputStream(BUF_SIZ);

			try {
				ChartUtilities.writeChartAsPNG(out, jFreeChart, 640, 480);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				appendMessage("Error saving chart image: " + e.toString());
				return;
			}

			// close any chart stream that's already open
			if (null != chartStream) {
				try {
					chartStream.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}

			// open a new chart stream from the output stream buffer contents
			chartStream = new ByteArrayInputStream(out.toByteArray());

			streamedChartObject = new DefaultStreamedContent(chartStream,
					"image/png");

			streamedChartStale = false;
		}

	}

}
